package org.ricks.ioc.utils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadUtils {

    private static final int WAIT_TIME = 10;
    private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void shutdown(ExecutorService executor) {
        try {
            if (!executor.isTerminated()) {

                executor.shutdown();

                if (!executor.awaitTermination(WAIT_TIME, TIME_UNIT)) {
                    executor.shutdownNow();
                }

            }
        } catch (Exception e) {
            Logger.error("["+executor+"] is failed to shutdown! ", e);
        }
    }

    public static Runnable safeRunnable(Runnable runnable) {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } catch (Exception e) {
                    Logger.error("unknown exception", e);
                } catch (Throwable t) {
                    Logger.error("unknown error", t);
                }
            }
        };
    }


    // threadId -> (Thread, Executor)
    private static final Map<Long, Pair<Thread, Executor>> threadExecutorMap = new ConcurrentHashMap<>(Runtime.getRuntime().availableProcessors() * 8);

    public static void registerSingleThreadExecutor(Thread thread, Executor executor) {
        threadExecutorMap.put(thread.getId(), new Pair<>(thread, executor));
    }

    public static Executor executorByThreadId(long threadId) {
        var threadExecutor = threadExecutorMap.get(threadId);
        return threadExecutor == null ? null : threadExecutor.getValue();
    }

    /**
     * search for the corresponding thread by the thread id
     */
    public static Thread findThread(long threadId) {
        var threadExecutor = threadExecutorMap.get(threadId);
        if (threadExecutor != null) {
            return threadExecutor.getKey();
        }

        var group = Thread.currentThread().getThreadGroup();
        while (group != null) {
            var threads = new Thread[group.activeCount() * 2];
            var count = group.enumerate(threads, true);
            for (var i = 0; i < count; i++) {
                if (threadId == threads[i].getId()) {
                    return threads[i];
                }
            }
            group = group.getParent();
        }
        return null;
    }
}
